﻿/*                              
   Copyright 2011 Nils Kopal, Christopher Krüger, Simone Sauer, Dennis Nolte, Uni Duisburg-Essen

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Collections.ObjectModel;
using System.Windows.Threading;
using System.Threading;
using Sharebert.SharebertGUI.Properties;
using Sharebert.DownloadLibrary;
using System.Net;
using System.Timers;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Sharebert.OverlayLibrary.API;
using Sharebert.OverlayLibrary;
using Sharebert.LoggingLibrary;
using Sharebert.LoggingLibrary.API;

namespace Sharebert.SharebertGUI
{
    /// <summary>
    /// Interaktionslogik für MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private ObservableCollection<Result> entries = new ObservableCollection<Result>();
        private ObservableCollection<LogMessage> logMessages = new ObservableCollection<LogMessage>();
        private IOverlay overlay;
        private DownloadListener downloadListener;
        private DownloadClient downloadClient;
        private bool mayDownload = true;

        public MainWindow()
        {
            InitializeComponent();
            ListView.MouseDoubleClick += ListView_MouseDoubleClick;
            ListView.DataContext = entries;
            log.DataContext = logMessages;
            ILogger logger = new Logbert();
            logger.MessageLogged += Overlay_LogMessage;
            overlay = new Overlay(Settings.Default.Server, Settings.Default.Port, Settings.Default.ListenAddress, logger);
            overlay.BootstrapPingInverval = Settings.Default.ServerPingInterval;
            overlay.PeerPingInverval = Settings.Default.PeerPingInterval;
            overlay.Timeout = Settings.Default.PeerTimeout;
            overlay.SearchResultReceived += searchClient_SearchResultReceived;
            overlay.SearchRequestReceived += overlay_SearchRequestReceived;
            AddLogMessage("Client started!", LogType.Info);

            Loaded += MainWindow_Loaded;
            Closing += MainWindow_Closing;
            overlay.Connect();
        }

        private void overlay_SearchRequestReceived(object sender, SearchRequestReceivedEventArgs e)
        {
            try
            {
                string folder = null;
                if (App.FilesPath != null)
                {
                    folder = @App.FilesPath;
                }
                else
                {
                    folder = @Settings.Default.FilerOfferFolder;
                }
                var files = Directory.GetFiles(@folder, "*.*", SearchOption.TopDirectoryOnly);
                var offerFiles = new List<FileOffer>();
                var regex = new Regex(e.RegEx, RegexOptions.IgnoreCase);
                    
                foreach (var file in files)
                {
                    var info = new FileInfo(@file);
                    if (!info.Name.EndsWith(".part") && regex.IsMatch(info.Name))
                    {
                        offerFiles.Add(new FileOffer(info.Name, info.Length));
                    }
                }

                //display the count of all files we offer
                Dispatcher.BeginInvoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
                {
                    currentOffers.Content = files.Length;
                }
                , null);

                //we have nothing that matches to the regEx
                if (offerFiles.Count == 0)
                {
                    return;
                }

                overlay.Respond(offerFiles.ToArray(), e.RegEx, e.Hash,overlay.MyIp, downloadListener.ListenPort);
                AddLogMessage(string.Format("Respond to request {0} with {1} files", e.RegEx, offerFiles.Count), LogType.Info);
            }
            catch (Exception ex)
            {
                AddLogMessage(string.Format("Respond to request {0} failed: {1}", e.RegEx, ex.Message), LogType.Error);
            }
        }

        void Overlay_LogMessage(object sender, LogEventArgs e)
        {
            AddLogMessage(e.Message, e.Type);
            if (e.Error != null)
            {
                Console.WriteLine(e.Error);
            }
        }        

        void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            try
            {
                overlay.Offline();
                if (downloadListener != null)
                    downloadListener.Stop();
            }
            catch (Exception)
            {
                //wtf
            }
            try
            {
                if (downloadClient != null)
                    downloadClient.StopDownload();
            }
            catch (Exception)
            {
                //wtf
            }
        }

        void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            if (downloadListener == null)
            {
                try
                {
                    downloadListener = new DownloadListener(IPAddress.Any, Settings.Default.DownloadListenPort);
                    downloadListener.DownloadersChanged += new EventHandler<DownloadersArgs>(downloadListener_DownloadersChanged);
                    downloadListener.ErrorOccured += new EventHandler<ErrorArgs>(downloadListener_ErrorOccured);
                    downloadListener.FileRequestReceived += new EventHandler<FileRequestEventArgs>(downloadListener_FileRequestReceived);
                    downloadListener.Start();
                }
                catch (Exception ex)
                {
                    Dispatcher.BeginInvoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
                    {
                        MessageBox.Show(this, "Can not start the DownloadListener: " + ex.Message, "Error", MessageBoxButton.OK);
                    }
                    , null);
                    Environment.Exit(-1);
                }
            }
        }

        void searchClient_ConnectionLost(object sender, EventArgs e)
        {
            Dispatcher.BeginInvoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
            {
                MessageBox.Show(this, "Connection lost, request failed.", "Error", MessageBoxButton.OK);
            }
            , null);
        }

        void searchClient_SearchResultReceived(object sender, SearchResultReceivedEventArgs e)
        {
            try
            {
                //Clear();
                foreach (var res in e.ResultList)
                {
                    foreach (var offer in res.OfferingFiles)
                    {
                        var result = new Result(res.ClientIP, res.ClientPort, offer.Name, offer.Size);                        
                        AddResult(result);
                    }
                }
            }
            catch (Exception ex)
            {
                Dispatcher.BeginInvoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
                {
                    MessageBox.Show(this, "Error occurred while processing search result: " + ex.Message, "Error", MessageBoxButton.OK);
                }
                , null);
            }
        }

        void ListView_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            if (mayDownload)
            {
                if (sender is ListView && ((ListView)sender).SelectedItem is Result)
                {
                    ListView lv = (ListView)sender;
                    Result result = (Result)lv.SelectedItem;
                    Dispatcher.BeginInvoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
                    {
                        MessageBoxResult res = MessageBox.Show(this, "Do you wish to download this file?", "Download", MessageBoxButton.YesNo);
                        if (res == MessageBoxResult.Yes)
                        {
                            new Thread(delegate()
                            {
                                try
                                {
                                    IPAddress ip = IPAddress.Parse(result.Ip);

                                    string folder = null;
                                    if (App.FilesPath != null)
                                    {
                                        folder = @App.FilesPath;
                                    }
                                    else
                                    {
                                        folder = @Settings.Default.FilerOfferFolder;
                                    }
                                    downloadClient = new DownloadClient(ip, result.Port);
                                    downloadClient.ErrorOccurred += new EventHandler<ErrorArgs>(downloadClient_ErrorOccured);
                                    downloadClient.DownloadStatusChanged += new EventHandler<DownloadStatusEventArgs>(downloadClient_DownloadStatusChanged);
                                    downloadClient.FileCompleted += new EventHandler<FileCompletedEventArgs>(downloadClient_FileCompleted);
                                    downloadClient.FileNotFound += new EventHandler<FileNotFoundEventArgs>(downloadClient_FileNotFound);
                                    downloadClient.TimedOut += new EventHandler<EventArgs>(downloadClient_TimedOut);
                                    downloadClient.RequestFile(result.File, @folder + "\\" + result.File + ".part", (int)result.Size);
                                    mayDownload = false;
                                    Dispatcher.BeginInvoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
                                    {
                                        stackPanel1.Visibility = Visibility.Visible;
                                    }
                                    , null);
                                }
                                catch (Exception ex)
                                {
                                    mayDownload = true;
                                    Dispatcher.BeginInvoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
                                    {
                                        MessageBox.Show(this, "Can not start the Download: " + ex.Message, "Error", MessageBoxButton.OK);
                                        stackPanel1.Visibility = Visibility.Hidden;
                                    }
                                    , null);
                                }
                            }) { Name = "DownloadThread" }.Start();
                        }
                    }
                    , null);
                }
            }
            else
            {
                Dispatcher.BeginInvoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
                {
                    MessageBox.Show(this, "Another download is still in progress, please wait until it has finished.", "Notice", MessageBoxButton.OK);
                }
                , null);
            }
                
        }

        private object _lockObject = new object();

        private void AddResult(Result item)
        {
            lock (_lockObject)
            {
                Dispatcher.Invoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
                {
                    foreach (var r in entries)
                    {
                        if (r.File.Equals(item.File) &&
                           r.Size.Equals(item.Size))
                        {
                            return; // we already have such a result in our list
                        }
                    }
                    entries.Add(item);
                }, item);
            }
        }        

        private void AddLogMessage(string message, LogType type)
        {
            var lm = new LogMessage(message, DateTime.Now.ToLongTimeString(), type);
            Dispatcher.Invoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
            {
                logMessages.Insert(0, lm);
                if (logMessages.Count > 20)
                    logMessages.RemoveAt(logMessages.Count - 1);
            }, message);
        }

        public void Clear()
        {
            lock (_lockObject)
            {
                Dispatcher.Invoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
                {
                    entries.Clear();
                    logMessages.Clear();
                }, null);
            }
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            Clear();
            string searchInput = textBox1.Text;
            try
            {                
                if (overlay != null)
                    overlay.Search(searchInput);
            }
            catch (ArgumentException)
            {
                Dispatcher.BeginInvoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
                {
                    MessageBox.Show(this, "Your search input is invalid. Please enter a valid regular expression.", "Error", MessageBoxButton.OK);
                }
                , null);
            }
            catch (Exception ex)
            {
                Dispatcher.BeginInvoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
                {
                    MessageBox.Show(this, "Error occurred while requesting search results: " + ex.Message, "Error", MessageBoxButton.OK);
                }
                , null);
            }
        }

        private void downloadClient_TimedOut(object sender, EventArgs e)
        {
            Dispatcher.BeginInvoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
            {
                MessageBox.Show(this, "Timeout occurred", "TimedOut", MessageBoxButton.OK);
            }
            , null);
            mayDownload = true;
            Dispatcher.BeginInvoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
            {
                stackPanel1.Visibility = Visibility.Hidden;
            }
            , null);
        }

        private void downloadClient_FileNotFound(object sender, FileNotFoundEventArgs e)
        {
            Dispatcher.BeginInvoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
            {
                MessageBox.Show(this, "The file " + e.Filename + " was not found.", "File not found", MessageBoxButton.OK);
            }
            , null);
            mayDownload = true;
            Dispatcher.BeginInvoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
            {
                stackPanel1.Visibility = Visibility.Hidden;
            }
            , null);
        }

        private void downloadClient_FileCompleted(object sender, FileCompletedEventArgs e)
        {
            try
            {
                var fileInfo = new FileInfo(e.Tempfilename);
                var oldname = fileInfo.FullName;
                var newname = fileInfo.Directory + "\\" + e.Filename;

                if (System.IO.File.Exists(@newname))
                {
                    System.IO.File.Delete(@newname);
                }

                System.IO.File.Move(@oldname, @newname);
                Dispatcher.BeginInvoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
                {
                    MessageBox.Show(this, "File '" + e.Filename + "' successfully downloaded.", "Downloaded", MessageBoxButton.OK);
                }
                , null);
            }
            catch (Exception ex)
            {
                Dispatcher.BeginInvoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
                {
                    MessageBox.Show(this, "Error processing the downloaded file: " + ex.Message, "Error", MessageBoxButton.OK);
                }
                , null);
            }
            finally
            {
                mayDownload = true;
                Dispatcher.BeginInvoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
                {
                    stackPanel1.Visibility = Visibility.Hidden;
                }
                , null);
            }
        }

        private void downloadClient_DownloadStatusChanged(object sender, DownloadStatusEventArgs e)
        {
            Dispatcher.BeginInvoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
            {
                progressBar1.Value = e.PercentageFinished * 100;
                progressBarText.Content = string.Format("Downloading {0} at {1:0.00} kb/sec", e.Filename, (double)e.BytesPerSecond / 1024);
            }
            , null);
        }

        private void downloadClient_ErrorOccured(object sender, ErrorArgs e)
        {
            mayDownload = false;
            Dispatcher.BeginInvoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
            {
                MessageBox.Show(this, "Error occurred: " + e.Exception.Message, "Error", MessageBoxButton.OK);
            }
            , null);
            Environment.Exit(-1);
        }

        private void downloadListener_FileRequestReceived(object sender, FileRequestEventArgs e)
        {
            try
            {
                string folder = null;
                if (App.FilesPath != null)
                {
                    folder = @App.FilesPath;
                }
                else
                {
                    folder = @Settings.Default.FilerOfferFolder;
                }
                e.DownloadClientHandler.SendFile(@folder + e.Filename);                
            }
            catch (Exception)
            {
                //wtf?
            }
        }

        private void downloadListener_ErrorOccured(object sender, ErrorArgs e)
        {
            Dispatcher.BeginInvoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
            {
                MessageBox.Show(this, "Error occurred: " + e.Exception.Message, "Error", MessageBoxButton.OK);
            }
            , null);
            Environment.Exit(-1);
        }

        private void downloadListener_DownloadersChanged(object sender, DownloadersArgs e)
        {
            Dispatcher.BeginInvoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
            {
                currentDownloads.Content = e.ActiveDownloads;
            }
            , null);
        }

        private void textBox1_KeyDown(object sender, KeyEventArgs e)
        {
            if(e.Key == Key.Return)
            {
                button1_Click(this,new RoutedEventArgs());
            }
        }

    }

    class Result
    {
        private string ip;
        private int port;
        private string file;
        private long size;

        public Result(string ip, int port, string file, long size)
        {
            this.ip = ip;
            this.port = port;
            this.file = file;
            this.size = size;
        }

        public string Ip { get{ return ip; } }
        public int Port { get { return port; } }
        public string File { get { return file; } }
        public long Size { get { return size; } }
        public string SizeRepresentation
        {
            get
            {
                if (size > 1024 * 1024 * 1024)
                {
                    return Math.Round((double)size / (1024 * 1024 * 1024), 2) + " Gigabytes";    
                }
                if(size > 1024*1024)
                {
                    return Math.Round((double)size / (1024 * 1024), 2) + " Megabytes";    
                }
                if(size > 1024)
                {
                    return Math.Round((double)size / (1024),2) + " Kilobytes";    
                }
                return size + " Bytes";    
            }
        }
    }

    class LogMessage
    {
        private string message;
        private string time;
        private LogType type;

        public string Message { get { return message; } set { message = value; } }
        public string Time { get { return time; } set { time = value; } }
        public LogType Type { get { return type; } set { type = value; } }

        public LogMessage(string message, string time, LogType type)
        {
            this.message = message;
            this.time = time;
            this.type = type;
        }
    }
}
